#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <mpi.h>

#define W_SIZE 1000
#define P_SIZE 500


int isEndOfPhrase(char c);
void findCutoffs(int *cutoffs, char *text, long int fileSize, int nThreads);
void ClearString(char *String);
int W_isPalindrome(char *String);
int P_isPalindrome(char *String);
int isEndOfWord(char c);
int inTheWord(char c);
void CountPalindrome(char *StringTemp, long int sizeFile, int numberOfPalindromes[]);


int main(int argc, char *argv[]){

        int nPalindW = 0, nPalindP = 0;
        int fileSize;
        FILE *f = fopen("shakespe.txt", "r");
        int *cutoffs;
        int destiny = 0, source = 0;
        int begin, end;
        int n[2];
        int threadNumber;
        int nThreads;
        char text[6000000];
        int i;
        MPI_Status Stat;
        int outBegin, outEnd;

        MPI_Init(&argc, &argv);
        MPI_Comm_rank(MPI_COMM_WORLD, &threadNumber); /*Obtem o numero da Thread*/
        MPI_Comm_size(MPI_COMM_WORLD, &nThreads);

        cutoffs = (int*) malloc((nThreads + 1) * sizeof(int));

        if(threadNumber == 0){ /*Trecho Sequencial do Codigo*/
            if(f == NULL) {
                printf("Nao foi possivel abrir o arquivo\n");
                exit(1);
            }
            /*
             * calcula o tamanho do arquivo de texto
             */
            fseek(f, 0, SEEK_END);
            fileSize = ftell(f);
            /*
             * retorna o cursor para o inicio do arquivo
             */
            rewind(f);
            /*
             * salva todo o arquivo no vetor 'text'
             */
            fread(text, sizeof(char), fileSize + 1, f);
            /*
             * encontra as posicoes do vetor 'text' dentro das quais cada
             * thread vai computar
             */
            findCutoffs(cutoffs , text, fileSize, nThreads);
           
            for(i = 1; i < nThreads; i++) {
                outBegin = cutoffs[i];
                outEnd = cutoffs[i + 1];
                /*
                 * envia para o i-esimo processo os seguintes dados:
                 * - outBegin - inicio do bloco de texto a ser processado
                 * - outEnd - fim do bloco de texto a ser processado
                 * - text[outBegin] - vetor com o bloco de texto a ser processado
                 */
                MPI_Send(&outBegin, 1, MPI_INT, i, 1, MPI_COMM_WORLD);
                MPI_Send(&outEnd, 1, MPI_INT, i, 1, MPI_COMM_WORLD);
                MPI_Send(&text[outBegin], outEnd - outBegin, MPI_CHAR, i, 1, MPI_COMM_WORLD);
            }  
        } else {
            /*
             * recebe os seguintes dados:
             * - begin - inicio do bloco de texto a ser processado
             * - end - fim do bloco de texto a ser processado
             * - text[begin] - vetor com o bloco de texto a ser processado
             */
            MPI_Recv(&begin, 1, MPI_INT, source, 1, MPI_COMM_WORLD, &Stat);
            MPI_Recv(&end, 1, MPI_INT, source, 1, MPI_COMM_WORLD, &Stat);
            MPI_Recv(&text[begin], end - begin, MPI_CHAR, source, 1, MPI_COMM_WORLD, &Stat);
        }

        /*
         * delimita o inicio e fim do bloco de texto a ser processado
         * pelo processo 0
         */
        if(threadNumber == 0) {
            begin = cutoffs[0]; /* obtem o inicio do bloco a ser analisado */
            end = cutoffs[1]; /* obtem o fim do bloco a ser analisado */
        }
        /*
         * calcula o nro de palindromos
         */
        CountPalindrome(&text[begin], end - begin, n);

        MPI_Reduce(&n[0], &nPalindW, 1, MPI_INT, MPI_SUM, destiny, MPI_COMM_WORLD);
        MPI_Reduce(&n[1], &nPalindP, 1, MPI_INT, MPI_SUM, destiny, MPI_COMM_WORLD);
        if(threadNumber == 0){
                printf("Total of Palindromes found:\nWords:\t%d\nPhrases:  %d\n\n", nPalindW, nPalindP);
        }
        MPI_Finalize();


        return 0;
}



/*
 * Verifica se um caracter eh um dos caracteres que
 * delimitam frases
 * @ ENTRADA
 *   letter - caracter que se quer verificar
 * @ SAIDA
 *   int - 1 se o caracter for um delimitador de frase,
 *         e 0 caso contrario
 */
int isEndOfPhrase(char c)
{
        //if the char is one of this symbols, its an end of phrase
        if(c == '.' || c == ';' || c == '(' || c == ')' || c == '"' ||
                        c == ':' || c == '<' || c == '>' || c == '{' || c == '}' ||
                        c == '[' || c == ']' || c == '|' || c == '&' || c == '!' ||
                        c == '?' || c == '\n'|| c == '\t'|| c == '\r' )
                return 1;
        else return 0;
}

/*
 * Encontra as posicoes do vetor com o texto dentro das quais
 * cada thread vai atuar
 * @ ENTRADA
 *   *text - vetor de caracter com o texto analisado
 *   fileSize - tamanho do vetor de texto
 *   nThreads - numero de threads
 * @ SAIDA
 *   *cutoffs - vetor de inteiros com as posicoes que delimitam
 *              cada bloco
 */
void findCutoffs(int *cutoffs, char *text, long int fileSize, int nThreads)
{
        int blockSize;
        int i;

        /*
         * calcula o tamanho aproximado que os blocos de caracteres que cada tread
         * vai computar devem ter
         */
        blockSize = fileSize / nThreads;
        cutoffs[0] = 0; /* o inicio do 1o bloco eh sempre zero */
        /*
         * encontra, no texto, o proximo final de frase a partir
         * de cada ponto que deveria marcar o fim de bloco
         */
        for(i = 1; i < nThreads; i++) {
                /* recebe a posicao que deveria marcar o fim do i-esimo bloco */
                cutoffs[i] = i * blockSize;
                while( !isEndOfPhrase(text[ cutoffs[i] ]) )
                        cutoffs[i]++;
                cutoffs[i]++;
        }
        /* o final do ultimo bloco sempre eh o fim do texto */
        cutoffs[i] = fileSize;
}

/*
 * Function: ClearString
 * Params: char *String
 * return: VOID
 * Clean the String
 */
void ClearString(char *String){
        //Gets the size of the string and change every char for a '\0'
        int i, size = strlen(String);
        for(i=0; i< size; i++){
                String[i] = '\0';
        }
}

/*
 * Function: W_isPalindrome
 * Params: char *String
 * return: int: 1 - TRUE; 0 - FALSE
 * Verify if the String received is a palindrome.
 * There's only one difference between this function and the P_isPalindrome:
 * Here we use an array of W_SIZE = 30 and the other uses P_SIZE = 500
 */
int W_isPalindrome(char *String){
        // We create the local variables and set the initial values
        char inverse_String[W_SIZE];
        int count, size = strlen(String);
        int i;
        count = size-1;
        ClearString(inverse_String);

        // If the String isn't just a letter, i.e: 'a', verify if its a palindrome
        if(strlen(String) > 1){
                //transform all the letters to lower chars
                for(i=0; i<size; i++){
                        String[i] = tolower(String[i]);
                }

                //copy the string to a aux string in the inverse
                for(i=0; i<size; i++){
                        inverse_String[i] = String[count];
                        count--;
                }
                //compare both strings
                if(strcmp(inverse_String, String) == 0){
                        return 1;
                }
                else{
                        return 0;
                }
        }
        else return 0;
}

/*
 * Function: P_isPalindrome
 * Params: char *String
 * return: int: 1 - TRUE; 0 - FALSE
 * Verify if the String received is a palindrome.
 * There's only one difference between this function and the W_isPalindrome:
 * Here we use an array of W_SIZE = 500 and the other uses W_SIZE = 30
 */
int P_isPalindrome(char *String){
        // We create the local variables and set the initial values
        char inverse_String[P_SIZE];
        int count, size = strlen(String);
        int i;

        count = size-1;
        ClearString(inverse_String);

        // If the String isn't just a letter, i.e: 'a', verify if its a palindrome
        if(strlen(String) > 1){
                //transform all the letters to lower chars
                for(i=0; i<size; i++){
                        String[i] = tolower(String[i]);
                }

                //copy the string to a aux string in the inverse
                for(i=0; i<size; i++){
                        inverse_String[i] = String[count];
                        count--;
                }

                //compare both strings
                if(!strcmp(inverse_String, String)){
                        return 1;
                }
                else{
                        return 0;
                }
        }
        else return 0;
}

/*
 * Function: inEndOfWord
 * Param: char c
 * return int: 1 _ TRUE; 0 - FALSE
 * Verify if the char indicates an end of word
 */
int isEndOfWord(char c){
    //if the char is one of this symbols, its just an end of word
    if(c == ' ' || c == ',' || c == '-' || c == '_'  || c == 39)
        return 1;
    else return 0;
}

/*
 * Function: inTheWord
 * Param: char c
 * return int: 1 _ TRUE; 0 - FALSE
 * Verify if the char is in the word
 */
int inTheWord(char c){
    //if the char is one of this symbols, its not the final of the word
    if(((c >= 'A') && (c <= 'Z')) || ((c >= 'a') && (c <= 'z')) || ((c >= '0') && (c <= '9')))
        return 1;
    else return 0;
   
}

/*
 * Function: CountPalindrome
 * Param: char *StringTemp: vetor com o texto a ser analisado
 *        long int sizeFile: tamanho do vetor StringTemp
 *        int numberOfPalindromes[]: One array with the number of palindromes found
                      [0] Number of Palindrome words
                      [1] Number of Palindrome phrases
 * Verify if each word and sentence from the file is a palindrome.
 */
void CountPalindrome(char *StringTemp, long int sizeFile, int numberOfPalindromes[]){
    // Create and Set the variables and its initials values
    char Word[W_SIZE];
    char Phrase[P_SIZE];
    char c;
    int counter=0;
    long int i;
   
   
    numberOfPalindromes[0] = 0;
    numberOfPalindromes[1] = 0;
   
    ClearString(Word);
   
    // For each char of the file, analyses if indicates an end of word, phrase or is in the middle of word
    for(i=0; i<sizeFile; i++){
        c = StringTemp[i];
        //If it is a letter or number, continue the analysis
        if( inTheWord(c) ){
            Word[counter] = c;
            counter++;
        }
        //If it is just an end of word, verify if is a palindrome
        else if( isEndOfWord(c) ){
            strcat(Phrase, Word);
            if(W_isPalindrome(Word) && strlen(Word) > 1){
                numberOfPalindromes[0]++;
            }
           
            ClearString(Word);
            counter=0;
        }
        //If is an end of Phrase, verify if the last word is a palindrome and then verify if the phrase is a palindrome
        else if( isEndOfPhrase(c) ){
           
            strcat(Phrase, Word);
           
            if(W_isPalindrome(Word) && strlen(Word) > 1){
                numberOfPalindromes[0]++;
            }
           
            if(P_isPalindrome(Phrase) && strlen(Phrase) > 1){
                numberOfPalindromes[1]++;
            }
            ClearString(Word);
            ClearString(Phrase);
            counter = 0;
        }
    }
}
